﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WorldBankBBS.Common;
using WorldBankBBS.Comms;
using WorldBankBBS.Data;
using WorldBankBBS.Handlers;
using WorldBankBBS.Logic;
using WorldBankBBS.Translators;
using Session = WorldBankBBS.Logic.Session;
using Thread = System.Threading.Thread;

namespace WorldBankBBS
{
    public partial class BBSService : ServiceBase
    {
        private TcpListener _listener;
        private TcpListener _cbmListener;
        private TcpListener _ansiListener;

        private Thread _listenerThread;
        private Thread _cbmListenerThread;
        private Thread _ansiListenerThread;

        private List<ITcpConnection> _clients;

        private ModelHelper _modelHelper = null;

        public BBSService()
        {
            InitializeComponent();

            _modelHelper = new ModelHelper();

            _modelHelper.BeginNewContext();
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                _modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Starting Service", DateTime.UtcNow));
                _modelHelper.Context.SaveChanges();

                _clients = new List<ITcpConnection>();
                _listener = new TcpListener(IPAddress.Any, 23);
                _cbmListener = new TcpListener(IPAddress.Any, 6400);
                _ansiListener = new TcpListener(IPAddress.Any, 6401);
                _listenerThread = new Thread(ListenForClients);
                _cbmListenerThread = new Thread(ListenForCbmClients);
                _ansiListenerThread = new Thread(ListenForAnsiClients);
                _listenerThread.Start();
                _cbmListenerThread.Start();
                _ansiListenerThread.Start();

                _modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Started Service", DateTime.UtcNow));
                _modelHelper.Context.SaveChanges();
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
            }
        }

        private void ListenForClients()
        {
            try
            {
                using (var modelHelper = new ModelHelper())
                {
                    modelHelper.BeginNewContext();

                    _listener.Start();

                    modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Listening for clients on 23",
                        DateTime.UtcNow));
                    modelHelper.Context.SaveChanges();


                    while (true)
                    {
                        var client = _listener.AcceptTcpClient();

                        modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Client accepted on 23",
                            DateTime.UtcNow));
                        modelHelper.Context.SaveChanges();

                        Thread clientThread = new Thread(new ParameterizedThreadStart(HandleNewClient));
                        clientThread.Start(client);
                        Thread.Sleep(100);
                    }
                }
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
            }
        }

        private void ListenForCbmClients()
        {
            try
            {
                using (var modelHelper = new ModelHelper())
                {
                    modelHelper.BeginNewContext();

                    _cbmListener.Start();

                    modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Listening for clients on 6400",
                        DateTime.UtcNow));
                    modelHelper.Context.SaveChanges();

                    while (true)
                    {
                        var client = _cbmListener.AcceptTcpClient();

                        modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Client accepted on 6400",
                            DateTime.UtcNow));
                        modelHelper.Context.SaveChanges();

                        Thread clientThread = new Thread(new ParameterizedThreadStart(HandleNewCbmClient));
                        clientThread.Start(client);
                        Thread.Sleep(100);
                    }
                }
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
            }
        }

        private void ListenForAnsiClients()
        {
            try
            {
                using (var modelHelper = new ModelHelper())
                {
                    modelHelper.BeginNewContext();

                    _ansiListener.Start();

                    modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Listening for clients on 6401",
                        DateTime.UtcNow));
                    modelHelper.Context.SaveChanges();

                    while (true)
                    {
                        var client = _ansiListener.AcceptTcpClient();

                        modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Client accepted on 6401",
                            DateTime.UtcNow));
                        modelHelper.Context.SaveChanges();

                        Thread clientThread = new Thread(new ParameterizedThreadStart(HandleNewAnsiClient));
                        clientThread.Start(client);
                        Thread.Sleep(100);
                    }
                }
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
            }
        }

        private void HandleNewCbmClient(object client)
        {
            TcpClient tcpClient = client as TcpClient;

            Thread watcher = new Thread(Watcher);
            watcher.Start(new Tuple<Thread, TcpClient>(Thread.CurrentThread, tcpClient));

            var user = new User();
            var commClient = new TcpCommunicator(tcpClient);
            //commClient.Pause += Handlers.PauseHandler.OnPause;

            _clients.Add(commClient);

            try
            {
                using (var session = new Session(commClient, user) { Encoding = "PETSCII" })
                {
                    commClient.Session = session;
                    Bbs(session);

                    commClient.Terminate();
                }
            }
            catch (SessionAbortedException sae)
            {
                commClient.Terminate();
            }
            catch (ThreadAbortException tae)
            {
                commClient.Terminate();
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
                commClient.Terminate();
            }
        }

        private void HandleNewAnsiClient(object client)
        {
            TcpClient tcpClient = client as TcpClient;

            Thread watcher = new Thread(Watcher);
            watcher.Start(new Tuple<Thread, TcpClient>(Thread.CurrentThread, tcpClient));

            var user = new User();
            var commClient = new TcpCommunicator(tcpClient);
            //commClient.Pause += Handlers.PauseHandler.OnPause;

            _clients.Add(commClient);

            try
            {
                using (var session = new Session(commClient, user) { Encoding = "ANSI" })
                {
                    commClient.Session = session;

                    Bbs(session);

                    commClient.Terminate();
                }
            }
            catch (SessionAbortedException sae)
            {
                commClient.Terminate();
            }
            catch (ThreadAbortException tae)
            {
                commClient.Terminate();
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
                commClient.Terminate();
            }
        }

        private void Watcher(object parameter)
        {
            try
            {
                var thread = ((Tuple<Thread, TcpClient>)parameter).Item1;
                var client = ((Tuple<Thread, TcpClient>)parameter).Item2;

                while (IsConnected(client)) Thread.Sleep(100);

                thread.Abort();
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());

            }
        }

        public bool IsConnected(TcpClient tcpClient)
        {
            try
            {
                if (tcpClient != null && tcpClient.Client != null && tcpClient.Client.Connected)
                {
                    // Detect if client disconnected
                    if (tcpClient.Client.Poll(0, SelectMode.SelectRead))
                    {
                        byte[] buff = new byte[1];
                        if (tcpClient.Client.Receive(buff, SocketFlags.Peek) == 0)
                        {
                            // Client disconnected
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    }

                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch
            {
                return false;
            }
        }

        private void Bbs(Session session)
        {
            var dispatcher = BbsDispatcher.GetDispatcher(session);

            dispatcher.ApplyHandlers(session);

            var bbs = dispatcher.GetObject<BbsMain>("WorldBankBBS.Logic.BbsMain");

            bbs.Start();
        }


        private void HandleNewClient(object client)
        {
            TcpClient tcpClient = client as TcpClient;

            Thread watcher = new Thread(Watcher);
            watcher.Start(new Tuple<Thread, TcpClient>(Thread.CurrentThread, tcpClient));

            var user = new User();
            var commClient = new TcpCommunicator(tcpClient);
            //commClient.Pause += Handlers.PauseHandler.OnPause;

            _clients.Add(commClient);

            try
            {
                using (var session = new Session(commClient, user) { Encoding = "ASCII" })
                {
                    commClient.Session = session;

                    Bbs(session);

                    commClient.Terminate();
                }
            }
            catch (SessionAbortedException sae)
            {
                commClient.Terminate();
            }
            catch (ThreadAbortException tae)
            {
                commClient.Terminate();
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
                commClient.Terminate();
            }
        }



        protected override void OnStop()
        {
            try
            {
                _modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Stopping Service", DateTime.UtcNow));
                _modelHelper.Context.SaveChanges();

                _listenerThread.Abort();
                _cbmListenerThread.Abort();
                _ansiListenerThread.Abort();

                foreach (var client in _clients)
                {
                    client.Terminate();
                }

                _modelHelper.AddToLogs(Log.CreateLog(Guid.NewGuid(), "Information", "Stopped Service", DateTime.UtcNow));
                _modelHelper.Context.SaveChanges();

                _modelHelper.EndCurrentContext();
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Application", e.ToString());
            }
        }

        internal void Process()
        {
            OnStart(null);

            while (true) Thread.Sleep(100);
        }
    }
}
